# =================================================================
#
# -*- coding:utf-8 -*-
# @Date: 2020/2/16
# @Author: Tigerots
#
# =================================================================


import os
import os.path
import sys

#一定要在import之前, 否则打包容易失败
if hasattr(sys, 'frozen'):
    os.environ['PATH'] = sys._MEIPASS + ";" + os.environ['PATH']



import socket
import configparser
from typing import Optional

import MainForm
import signal_emit
import threading
import serial.tools.list_ports


from PyQt5 import QtGui
from PyQt5.QtWidgets import QLabel, QFileDialog
from PyQt5.QtWidgets import QMainWindow
from PyQt5.QtWidgets import QApplication

from my_serial_moudle import *
import crc

# 以下为窗口应用 ============================================================
# 主窗口类
class MainLogic(QMainWindow, MainForm.Ui_MainWindow , signal_emit.SignalEmit, SerialSetting):

    def __init__(self, th_ser_recv):  # 主窗口的执行
        super(MainLogic, self).__init__()
        self.setupUi(self)

<<<<<<< HEAD
        with open('qss/blue.txt') as file:
            str = file.readlines()
            str = ''.join(str).strip('\n')
            app.setStyleSheet(str)
=======
        # self.btn_iap_nopro_start.setStyleSheet('''
        #                                         QPushButton{background:#F76677;
        #                                                     border-radius:10px;}
        #                                         QPushButton:hover{background:red;}''')
        # self.btn_iap_nopro_mode.setStyleSheet('''
        #                                         QPushButton{background:#F7D674;
        #                                                     border-radius:10px;}
        #                                         QPushButton:hover{background:yellow;}''')
        # self.btn_iap_nopro_reboot.setStyleSheet('''
        #                                         QPushButton{background:#6DDF6D;
        #                                                     border-radius:10px;}
        #                                         QPushButton:hover{background:green;}''')
        # self.btn_open_serial.setStyleSheet('''
        #                                         QPushButton{background:#57ABFF;
        #                                                     border-radius:10px;}
        #                                         QPushButton:hover{background:#5273A4;}''')

        # with open('qss/blue.txt') as file:
        #     str = file.readlines()
        #     str = ''.join(str).strip('\n')
        #     app.setStyleSheet(str)
>>>>>>> 9b497527f71c06f79132bad6be3fdba43e0bdfec

        # 串口接收相关变量
        self.ser = serial.Serial()
        self.ser_rx_buff = "" # 接收数据缓存区
        self.ser_rx_hex = []  # 接收数据缓存区
        self.ser_rx_timeout = False
        self.th_ser_recv = th_ser_recv # 串口接收线程, 使用的pyqt线程

        # 串口发送数据相关变量
        self.ser_send_buf = "" # 发送数据缓存区
        self.ser_cmd_buf = [] # 发送命令缓存区
        self.ser_send_cnt = 0 # 发送数据计数

        # 升级文件相关变量
        self.file_path_name = "" # 升级文件路径
        self.file_type = "" # 文件类型
        self.file_size = 0 # 文件大小(字节)

        # 协议相关变量
        self.pro_ask_en = True
        self.pro_ask_flag = 0 # 应答标志 0: 无需应答 1:等待应答 2:应答成功 11:应答超时 12:应答失败
        self.pro_ask_cmd = "" # 应答需要返回的命令
        self.pro_ask_data = ""  # 应答返回的内容

        # 升级模式
        self.is_use_pro = True # 是否是协议升级

        # 隐藏AT指令升级窗口
        self.gbox_nopro.hide()

        # 窗口禁止最大化, 禁止拉伸
        self.setFixedSize(self.width(), self.height())

        # ini文件相关
        self.read_from_config()  # 读配置文件

        # 通讯配置===================================================================
        # 波特率
        l_list = ["9600", "19200", "38400", "57600", "76800", "115200", ]
        self.cbx_band.addItems(l_list)
        if self.ini_band in l_list:
            self.cbx_band.setCurrentText(self.ini_band)
        else:
            self.cbx_band.setCurrentText("115200")

        # 串口号
        l_list = []
        p_list = list(serial.tools.list_ports.comports())
        if len(p_list) <= 0:
            l_list = ["COM1", "COM2", "COM3", "COM4", "COM5", "COM6"]
            self.cbx_com.addItems(l_list)
            print("没有发现端口!")
        else:
            for i in p_list:
                l_list.append(list(i)[0])
            print("可用端口名: ", l_list)
            self.cbx_com.addItems(l_list)
            if self.ini_com in l_list:
                self.cbx_com.setCurrentText(self.ini_com)

        l_list = ["请选择芯片类型 ... ",
                  "STM32F103xC 256K ",
                  "STM32F103xD 384K",
                  "STM32F103xE 512K", ]
        self.cbx_chip.addItems(l_list)

        # 通讯方式
        l_list = ["RS232 串口", "RS485 串口", "TCP 服务器", "TCP 客户端", ]
        self.cbx_comm_ck.addItems(l_list)

        # 固件信息====================================================================
        # 应答
        l_list = ["一问一答方式", "广播，无应答"]
        self.cbx_ask_type.addItems(l_list)
        # 包间隔
        l_list = ["1","10", "50", "100", "200", "500", "1000", "2000", "5000"]
        self.cbx_pack_gap.addItems(l_list)
        self.cbx_pack_gap.setCurrentText("10")
        self.delay_ms = int(self.cbx_pack_gap.currentText())
        # 包大小
        l_list = ["16", "64", "256", "1024",]
        self.cbx_pack_size.addItems(l_list)
        self.cbx_pack_size.setCurrentText("1024")
        self.pack_size = int(self.cbx_pack_size.currentText())

        # 适配设备类型
        l_list = ["通用类型",]
        self.cbx_dev_type.addItems(l_list)
        # 适配芯片类型
        l_list = ["STM32F103xC", "STM32F103xD", "STM32F103xE", ]
        self.cbx_chip_type.addItems(l_list)
        # 升级模式选择
        l_list = ["强制升级", "选择升级", ]
        self.cbx_up_type.addItems(l_list)
        # 固件版本掩码
        l_list = ["0xFFFF", ]
        self.cbx_soft_ver.addItems(l_list)
        # 硬件版本掩码
        l_list = ["0xFFFF", ]
        self.cbx_hard_ver.addItems(l_list)
        # 固件版本
        l_list = ["1001", "2001", "3001", "9001" ]
        self.cbx_iap_ver.addItems(l_list)
        # 固件类型
        l_list = ["hex", "bin", "fst", ]
        self.cbx_iap_type.addItems(l_list)
        # 固件加密方式
        l_list = ["无", "base64", "MD5", "sha256", "AES", "3DES",]
        self.cbx_iap_encode.addItems(l_list)

        # 擦除扇区==============================================================
        l_list = ["默认",]
        self.cbx_chip_buff_addr.addItems(l_list)
        l_list = ["默认",]
        self.cbx_chip_buff_size.addItems(l_list)

        # 恢复固件==============================================================
        l_list = ["备份固件", ]
        self.cbx_reset_iap_ver.addItems(l_list)

        # 数据包头==============================================================
        l_list = ["V2.0", ]
        self.cbx_pro_ver.addItems(l_list)
        l_list = ["通用类型 0xFF", "驾考设备 0x51", "检测线设备 0xF1",]
        self.cbx_pro_dev_type.addItems(l_list)
        l_list = ["通用地址 0xFF", "测试地址 0x01",]
        self.cbx_pro_dev_addr.addItems(l_list)
        l_list = ["设置-应答", "设置-无应答", "查询", "设备主动上报"]
        self.cbx_pro_cmd_ask.addItems(l_list)

        # 状态栏
        self.status = self.statusBar()
        self.S_Sta = QLabel("  欢迎使用弗斯特调测软件")
        self.S_IP = QLabel("版本号:V1.0")
        self.S_Port = QLabel("仅供内部使用")
        self.status.addPermanentWidget(self.S_Sta, stretch=6)
        self.status.addPermanentWidget(self.S_IP, stretch=2)
        self.status.addPermanentWidget(self.S_Port, stretch=2)

        self.btn_iap_nopro_start.setEnabled(0)
        self.btn_iap_nopro_reboot.setEnabled(0)

        self.btn_iap_start_US.setEnabled(1) # 启动升级
        self.btn_chip_buff_erases.setEnabled(0)   # 擦除扇区
        self.btn_reset_iap.setEnabled(0)   # 恢复出厂设置
        self.btn_iap_send_pack.setEnabled(0)   # 发送固件
        self.btn_iap_fine_reset.setEnabled(0) # 升级确认

        self.iap_pro_str = {"pro_head":     [0x3A, 0x5B, 0x7C],
                            "pro_ver":      [0x20],
                            "dev_type":     [0xFF],
                            "dev_addr":     [0xFF],
                            "cmd_tag":      [0x00],
                            "ask_tag":      [0x00],
                            "data_size":    [0],
                           # "data":         [],
                            "crc":          [0x00,0x00],
                            "pro_tail":     [0x7E], }

        self.iap_cmd_US = {"cmd_main":      [ord("U")],
                           "cmd_sub":       [ord("S")],
                           "ask_type":      [0x01],
                           "pack_gap":      [10],
                           "pack_size":     [1024],
                           "dev_type":      [0xFF],
                           "chip_type":     [0xFF, 0xFF, 0xFF, 0xFF],
                           "up_type":       [1],
                           "soft_ver":      [0xFF, 0xFF],
                           "hard_ver":      [0xFF, 0xFF],
                           "iap_ver":       [0x01, 0x10],
                           "iap_tpye":      [0x01],
                           "iap_size":      [0x00, 0x00, 0x00, 0x00],
                           "iap_encode":    [0x00],
                           "iap_res8":      [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], }

        # 擦除扇区命令
        self.iap_cmd_UE = {"cmd_main": [ord("U")],
                           "cmd_sub": [ord("E")],
                           "Erase_addr": [0x00, 0x10, 0x02, 0x08], # 0x08021000
                           "Erase_size": [0x00, 0x90, 0x01, 0x00],} # 0x19000

        # 发送固件命令
        self.iap_cmd_UD = {"cmd_main": [ord("U")],
                           "cmd_sub": [ord("D")],
                           "pack_total": [0xFF, 0xFF], # 总包数
                           "pack_index": [0xFF, 0xFF], # 包序号
                           "data_num": [0xFF, 0xFF]} # 数据长度
        # 恢复出厂设置
        self.iap_cmd_UR = {"cmd_main": [ord("U")],
                           "cmd_sub": [ord("R")],
                           "soft_ver": [0xFF, 0xFF],}

        self.connect()
        self.get_local_ip()
        self.cbx_chip.setCurrentIndex(1)

    # 绑定信号与槽Signals & slots
    def connect(self):
        # 按键 btn
        self.btn_open_serial.clicked.connect(self.btn_open_serial_func)
        # 按键 # 无协议升级
        self.btn_iap_nopro_mode.clicked.connect(self.btn_mode_nopro_func)# 进入无协议升级模式
        self.btn_iap_nopro_start.clicked.connect(self.btn_send_nopro_func)# 无协议升级(直接发送bin文件)
        self.btn_iap_nopro_reboot.clicked.connect(self.btn_end_nopro_func)# 升级完成, 设备复位
        self.btn_iap_fine_reset.clicked.connect(self.btn_iap_fine_reset_func)

        # 按键 # 协议升级
        self.btn_iap_start_US.clicked.connect(self.cbx_iap_cmd_US_func)# 启动升级
        self.btn_chip_buff_erases.clicked.connect(self.cbx_iap_cmd_UE_func)# 擦除扇区
        self.btn_reset_iap.clicked.connect(self.cbx_iap_cmd_UR_func)# 恢复出厂设置
        self.btn_iap_send_pack.clicked.connect(self.cbx_iap_cmd_UD_func)  # 发送固件

        # 清除接收窗口数据
        self.btn_clean_recv_win.clicked.connect(self.btn_clean_recv_win_fucn)

        # 列表框 combobox
        self.cbx_com.currentTextChanged.connect(self.cbx_com_func)
        self.cbx_band.currentTextChanged.connect(self.cbx_band_func)
        self.cbx_chip.currentTextChanged.connect(self.cbx_chip_func)
        self.cbx_comm_ck.currentIndexChanged.connect(self.cbx_comm_ck_func)
        # === 固件信息 US ===
        self.cbx_ask_type.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_pack_gap.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_pack_size.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_dev_type.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_chip_type.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_up_type.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_soft_ver.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_hard_ver.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_iap_ver.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_iap_type.currentTextChanged.connect(self.cbx_US_func)
        self.cbx_iap_encode.currentTextChanged.connect(self.cbx_US_func)
        self.lineEdit_iap_size.textChanged.connect(self.cbx_US_func)
        # === 擦除信息 UE ===

        # === 包头信息 ===
        self.cbx_pro_ver.currentTextChanged.connect(self.cbx_fst_pro_str_func)
        self.cbx_pro_cmd_ask.currentTextChanged.connect(self.cbx_fst_pro_str_func)
        self.cbx_pro_dev_addr.currentTextChanged.connect(self.cbx_fst_pro_str_func)
        self.cbx_pro_dev_type.currentTextChanged.connect(self.cbx_fst_pro_str_func)
        # 菜单栏
        self.menu_open_file.triggered.connect(self.menu_open_file_func)
        self.menu_about_my.triggered.connect(self.menu_about_my_func)
        self.menu_help_doc.triggered.connect(self.menu_help_doc_func)
        self.menu_tools_make.triggered.connect(self.menu_tools_make_func)
        self.act_rst_en.triggered.connect(
            lambda: print('恢复出厂设置-启用') or self.btn_reset_iap.setEnabled(1) or self.act_rst_dis.setChecked(False))
        self.act_rst_dis.triggered.connect(
            lambda: print('恢复出厂设置-禁用') or self.btn_reset_iap.setEnabled(0) or self.act_rst_en.setChecked(False))
        self.gbk.triggered.connect(
            lambda: print('gbk') or self.gb2312.setChecked(False) or self.utf_8.setChecked(False))
        self.gb2312.triggered.connect(
            lambda: print('gb2312') or self.gbk.setChecked(False) or self.utf_8.setChecked(False) )
        self.utf_8.triggered.connect(
            lambda: print('utf-8') or self.gbk.setChecked(False) or self.gb2312.setChecked(False))
        self.act_nopro_en.triggered.connect(
            lambda: print('启用无协议升级模式') or self.act_nopro_dis.setChecked(False) or self.gbox_nopro.show())
        self.act_nopro_dis.triggered.connect(
            lambda: print('禁用无协议升级模式') or self.act_nopro_en.setChecked(False) or self.gbox_nopro.hide())

        # 信号槽
        self.signal_write_msg.connect(self.signal_write_msg_func)
        # 定时器
        # self.timer_rx.timeout.connect(self.timer_rx_func)
        # 复选框 checkBox
        # self.checkBox_iot.toggled.connect(self.checkBox_iot_fun)
        self.cbx_set_hex_mode.toggled.connect(self.set_comm_hex_fun)
        self.cbx_is_ask_and_answer.toggled.connect(self.cbx_is_ask_and_answer_func)
        # 串口接收
        self.th_ser_recv.ser_recv_data_output.connect(self.ser_recv_func)


    # 找到回复数据开头
    @staticmethod
    def find_cmd_start(str_buf, str_len):

        cmd_flag = 0
        if str_len <= 13:
            return -1, 0, cmd_flag

        for i in range(str_len):
            if str_buf[i] == 0x3A and str_buf[i + 1] == 0x5B and str_buf[i + 2] == 0x7C: # 判断数据头(包头尾共12字节)
                try:
                    pack_len = int(((str_buf[i + 9] << 8) | str_buf[i + 8]))
                    try:
                        if str_buf[i + 12 + pack_len] == 0x7E:
                            cmd_flag = 3  # 接收到FST协议头
                            # print("收到协议数据, 数据长度为: {}".format(pack_len))
                            return i, (13 + pack_len), cmd_flag
                    except Exception as e:
                        print(e)
                except Exception as e:
                    print(e)

        return -1, 0, cmd_flag

    # FST 协议解析
    def fst_pro_analysis(self, data_buf, data_len):

        start_pos, pack_len, cmd_flag = self.find_cmd_start(data_buf, data_len)
        if start_pos == -1:
            return False, 0, 0

        if cmd_flag != 0:  # 如果存在符合协议的数据
            # 找到数据头尾，若头等于尾则数据无效
            end_pos = start_pos + pack_len
            pro_data_buff = data_buf[start_pos:end_pos]
            ask_tag = pro_data_buff[7]
            cmd_main = pro_data_buff[10]
            cmd_sub = pro_data_buff[11]

            if ask_tag != 0x80 and ask_tag != 0x81:

                msg = "\r\n=========== 设备应答 ===========\r\n"
                self.signal_write_msg.emit(msg)  # 发送日志消息
                msg = "命令编号: " + chr(cmd_main) + chr(cmd_sub) + "\r\n"
                self.signal_write_msg.emit(msg)  # 发送日志消息

                if ask_tag == 0x82:
                    msg = "应答错误: " + "0x82,设备地址错误" + "\r\n"
                elif ask_tag == 0x83:
                    msg = "应答错误: " + "0x83,设备类型错误" + "\r\n"
                elif ask_tag == 0x84:
                    msg = "应答错误: " + "0x84,命令编号错误" + "\r\n"
                elif ask_tag == 0x85:
                    msg = "应答错误: " + "0x85,数据长度错误" + "\r\n"
                elif ask_tag == 0x86:
                    msg = "应答错误: " + "0x86,数据内容错误" + "\r\n"
                elif ask_tag == 0x87:
                    msg = "应答错误: " + "0x87,校验错误" + "\r\n"
                elif ask_tag == 0x88:
                    msg = "应答错误: " + "0x88,命令执行失败" + "\r\n"
                elif ask_tag == 0x89:
                    msg = "应答错误: " + "0x89,设备忙无法响应" + "\r\n"
                elif ask_tag == 0x8A:
                    msg = "应答错误: " + "0x8A,不支持的协议类型" + "\r\n"
                elif ask_tag == 0x8B:
                    msg = "应答错误: " + "0x8B,指令超时" + "\r\n"
                elif ask_tag == 0x8F:
                    msg = "应答错误: " + "0x8F,未知错误" + "\r\n"
                elif ask_tag == 0x90:
                    msg = "应答错误: " + "0x90,扇区未擦除" + "\r\n"
                elif ask_tag == 0x91:
                    msg = "应答错误: " + "0x91,不支持的文件类型" + "\r\n"
                elif ask_tag == 0x92:
                    msg = "应答错误: " + "0x92,不支持的加密类型" + "\r\n"
                elif ask_tag == 0x93:
                    msg = "应答错误: " + "0x93,固件数据低于最小值" + "\r\n"
                elif ask_tag == 0x94:
                    msg = "应答错误: " + "0x94,固件数据超过最大值" + "\r\n"
                elif ask_tag == 0x95:
                    msg = "应答错误: " + "0x95,数据包超长" + "\r\n"

                self.signal_write_msg.emit(msg)  # 发送日志消息
                return False, cmd_main, cmd_sub

            if (cmd_main == ord('U') and cmd_sub == ord('S')) \
                    or (cmd_main == ord('U') and cmd_sub == ord('M')) \
                    or (cmd_main == ord('U') and cmd_sub == ord('E')) \
                    or (cmd_main == ord('U') and cmd_sub == ord('D')) \
                    or (cmd_main == ord('U') and cmd_sub == ord('R')) \
                    or (cmd_main == ord('U') and cmd_sub == ord('F')):

                msg = "\r\n=========== 设备应答 ===========\r\n"
                self.signal_write_msg.emit(msg)  # 发送日志消息
                msg = "命令编号: " + chr(cmd_main) + chr(cmd_sub) + "\r\n"
                self.signal_write_msg.emit(msg)  # 发送日志消息
                msg = "应答成功: " + hex(ask_tag) + "\r\n"
                self.signal_write_msg.emit(msg)  # 发送日志消息

                return True, cmd_main, cmd_sub

        return False, 0, 0

    # 串口接收数据
    def ser_recv_func(self, recv_hex_data, recv_ascii_data):

        # print("HEX数据: ")
        # print(recv_hex_data)
        self.ser_rx_hex += recv_hex_data
        print("# Receive content: ")
        print(recv_ascii_data)

        if self.cbx_is_ask_and_answer.isChecked(): # 一问一答模式
            if not self.is_use_pro: # 不使用协议

                msg = recv_ascii_data
                self.signal_write_msg.emit(msg)  # 发送日志消息
                self.ser_rx_buff += str(recv_ascii_data)

                if self.pro_ask_flag == 1: # 等待应答
                    ret = re.search(r"(ok)", self.ser_rx_buff)
                    if ret:
                        self.pro_ask_flag = 2 # 应答完成
                        self.ser_rx_hex = []
                        self.ser_rx_buff = ""
                        # print("& at cmd ask ok")

                    else:
                        print("& at cmd ask error")

            else: # 使用FST协议
                if self.pro_ask_flag == 1:  # 等待应答
                    # msg = recv_ascii_data
                    # self.signal_write_msg.emit(msg)  # 发送日志消息
                    # self.ser_rx_buff += str(recv_ascii_data)

                    ret, cmd_main, cmd_sub = self.fst_pro_analysis(self.ser_rx_hex, len(self.ser_rx_hex))
                    if ret:
                        self.ser_rx_hex = []
                        self.ser_rx_buff = ""
                        self.pro_ask_flag = 2 # 应答完成
                        # print("& FST pro ask ok")

                    else:
                        print("& FST pro ask error")

                else:
                    msg = recv_ascii_data
                    self.signal_write_msg.emit(msg)  # 发送日志消息

            if len(self.ser_rx_hex) >= 1024:
                self.ser_rx_hex = []
                self.ser_rx_buff = ""

        else:
            self.ser_rx_hex = []
            self.ser_rx_buff = ""


    # 发送固件信息,启动升级程序
    def cbx_iap_cmd_US_func(self):

        if not self.is_serial_open():
            return False

        elif not self.is_open_file():
            return False

        else:

            msg = "\r1. 单击 文件-打开升级文件 菜单,选择正确的升级文件(*.bin);\r" \
                  "2. 单击 擦除扇区 按钮,擦除固件缓存扇区(可以使用默认参数);\r" \
                  "3. 单击 发送固件 按钮,等待数据发送完成，升级较慢，耐心等待;\r" \
                  "4. 数据发送完成后(进度条100%),单击 升级确认 重启生效 按钮,启动新固件;\r" \
                  "5. 如升级后10秒内未进行第确认操作复位设备,设备将不会启用新固件;\r" \
                  "6. 注意!注意!注意! 进入升级模式后、升级完成前不要进行任何串口操作!!!\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息


            self.is_use_pro = True
            self.ser_send_cnt = 0
            self.ser_cmd_buf.clear()
            # 包头 ============================================================
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_head"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_ver"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_type"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_addr"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("cmd_tag"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("ask_tag"))
            t_data = self.iap_pro_str.get("data_size")
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            # 数据体 ===========================================================
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("cmd_main"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("cmd_sub"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("ask_type"))
            t_data = self.iap_cmd_US.get("pack_gap") # 包间隔
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            t_data = self.iap_cmd_US.get("pack_size") # 包大小
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("dev_type"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("chip_type"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("up_type"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("soft_ver"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("hard_ver"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("iap_ver"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("iap_tpye"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("iap_size"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("iap_encode"))
            self.ser_cmd_buf.extend(self.iap_cmd_US.get("iap_res8"))

            t_data = len(self.ser_cmd_buf[10:])
            self.ser_cmd_buf[8] = (int(t_data % 256))
            self.ser_cmd_buf[9] = (int(t_data / 256))
            # 包尾 ============================================================
            crc_buf = self.ser_cmd_buf[3:]
            self.iap_pro_str["crc"][0], self.iap_pro_str["crc"][1] = crc.calc_crc_hex(crc_buf)
            self.ser_cmd_buf.extend(self.iap_pro_str.get("crc"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_tail"))
            self.ser_send_data_bin(self.ser_cmd_buf)
            self.pro_ask_flag = 1
            print(self.ser_cmd_buf)

            msg = "\r\n发送启动远程升级命令: cmd = US\r\n"
            self.signal_write_msg.emit(msg)  # 发送日志消息
            self.btn_iap_nopro_mode.setEnabled(0)
            self.btn_chip_buff_erases.setEnabled(1)  # 擦除扇区
            self.btn_iap_send_pack.setEnabled(0)  # 发送固件
            self.btn_iap_fine_reset.setEnabled(1)  # 升级确认

    # 发送固件命令
    def cbx_iap_cmd_UR_func(self):

        if not self.is_serial_open():
            return False
        else:
            self.is_use_pro = True
            self.ser_send_cnt = 0
            self.ser_cmd_buf.clear()
            # 包头 ============================================================
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_head"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_ver"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_type"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_addr"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("cmd_tag"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("ask_tag"))
            t_data = self.iap_pro_str.get("data_size")
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            # self.ser_cmd_buf.extend(self.iap_pro_str.get("data"))
            # 数据体 ===========================================================
            self.ser_cmd_buf.extend(self.iap_cmd_UR.get("cmd_main"))
            self.ser_cmd_buf.extend(self.iap_cmd_UR.get("cmd_sub"))
            self.ser_cmd_buf.extend(self.iap_cmd_UR.get("soft_ver"))

            t_data = len(self.ser_cmd_buf[10:])
            self.ser_cmd_buf[8] = (int(t_data % 256))
            self.ser_cmd_buf[9] = (int(t_data / 256))
            # 包尾 ============================================================
            crc_buf = self.ser_cmd_buf[3:]
            self.iap_pro_str["crc"][0], self.iap_pro_str["crc"][1] = crc.calc_crc_hex(crc_buf)
            self.ser_cmd_buf.extend(self.iap_pro_str.get("crc"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_tail"))
            self.ser_send_data_bin(self.ser_cmd_buf)
            self.pro_ask_flag = 1
            print(self.ser_cmd_buf)

            msg = "\r\n发送恢复出厂设置命令: cmd = UR\r\n"
            self.signal_write_msg.emit(msg)  # 发送日志消息

    # 发送固件命令
    def cbx_iap_cmd_UD_func(self):

        if not self.is_serial_open():
            return False

        elif not self.is_open_file():
            return False

        else:
            self.ser_send_cnt = 0
            self.is_use_pro = True
            self.is_use_pro = True

            msg = "\r=== 正在发送数据，升级完成前不要进行任何操作, 请耐心等待... ===\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息

            if self.cbx_iap_type.currentIndex() == 0: # Hex
                self.th_send_hex = threading.Thread(target=self.th_send_ascii_fun)
                self.th_send_hex.start()
            else: # bin, fst
                self.th_send_bin = threading.Thread(target=self.th_send_bin_fst_fun)
                self.th_send_bin.start()

    # 擦除扇区命令
    def cbx_iap_cmd_UE_func(self):

        if not self.is_serial_open():
            return False
        else:
            self.is_use_pro = True
            self.ser_send_cnt = 0
            self.ser_cmd_buf.clear()
            # 包头 ============================================================
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_head"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_ver"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_type"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_addr"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("cmd_tag"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("ask_tag"))
            t_data = self.iap_pro_str.get("data_size")
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            # self.ser_cmd_buf.extend(self.iap_pro_str.get("data"))
            # 数据体 ===========================================================
            self.ser_cmd_buf.extend(self.iap_cmd_UE.get("cmd_main"))
            self.ser_cmd_buf.extend(self.iap_cmd_UE.get("cmd_sub"))
            self.ser_cmd_buf.extend(self.iap_cmd_UE.get("Erase_addr"))
            self.ser_cmd_buf.extend(self.iap_cmd_UE.get("Erase_size"))

            t_data = len(self.ser_cmd_buf[10:])
            self.ser_cmd_buf[8] = (int(t_data % 256))
            self.ser_cmd_buf[9] = (int(t_data / 256))
            # 包尾 ============================================================
            crc_buf = self.ser_cmd_buf[3:]
            self.iap_pro_str["crc"][0], self.iap_pro_str["crc"][1] = crc.calc_crc_hex(crc_buf)
            self.ser_cmd_buf.extend(self.iap_pro_str.get("crc"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_tail"))
            self.ser_send_data_bin(self.ser_cmd_buf)
            self.pro_ask_flag = 1
            print(self.ser_cmd_buf)

            msg = "\r\n发送擦除扇区命令: cmd = UE\r\n"
            self.signal_write_msg.emit(msg)  # 发送日志消息

            self.btn_chip_buff_erases.setEnabled(1)  # 擦除扇区
            self.btn_iap_send_pack.setEnabled(1)  # 发送固件
            self.btn_iap_fine_reset.setEnabled(1)  # 升级确认



    def btn_iap_fine_reset_func(self):

        if not self.is_serial_open():
            return False
        else:
            self.is_use_pro = True
            self.ser_send_cnt = 0
            self.ser_cmd_buf.clear()
            # 包头 ============================================================
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_head"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_ver"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_type"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_addr"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("cmd_tag"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("ask_tag"))
            t_data = self.iap_pro_str.get("data_size")
            self.ser_cmd_buf.append(int(t_data[0] % 256))
            self.ser_cmd_buf.append(int(t_data[0] / 256))
            # self.ser_cmd_buf.extend(self.iap_pro_str.get("data"))
            # 数据体 ===========================================================
            self.ser_cmd_buf.extend([ord("U")])
            self.ser_cmd_buf.extend([ord("F")])
            self.ser_cmd_buf.extend([0xFF])
            self.ser_cmd_buf.extend([0xFF])

            t_data = len(self.ser_cmd_buf[10:])
            self.ser_cmd_buf[8] = (int(t_data % 256))
            self.ser_cmd_buf[9] = (int(t_data / 256))
            # 包尾 ============================================================
            crc_buf = self.ser_cmd_buf[3:]
            self.iap_pro_str["crc"][0], self.iap_pro_str["crc"][1] = crc.calc_crc_hex(crc_buf)
            self.ser_cmd_buf.extend(self.iap_pro_str.get("crc"))
            self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_tail"))
            self.ser_send_data_bin(self.ser_cmd_buf)
            self.pro_ask_flag = 1
            print(self.ser_cmd_buf)

            msg = "\r\n升级确认, 重启设备: cmd = UF\r\n"
            self.signal_write_msg.emit(msg)  # 发送日志消息
            self.btn_iap_nopro_mode.setEnabled(1)
            self.btn_chip_buff_erases.setEnabled(0)  # 擦除扇区
            self.btn_iap_send_pack.setEnabled(0)  # 发送固件
            self.btn_iap_fine_reset.setEnabled(0)  # 升级确认


    # 无协议升级模式 head ===========================================================================
    # 进入无协议升级模式
    def btn_mode_nopro_func(self):

        if not self.is_serial_open():
            return False

        else:
            self.is_use_pro = False  # 不使用协议
            self.cbx_set_hex_mode.setChecked(0)
            self.ser_send_cnt = 0
            self.ser_cmd_buf.clear()

            at_cmd_e_bin = "at+upgrade=e,bin\r\n"
            print(at_cmd_e_bin)
            if self.pro_ask_en:
                self.pro_ask_flag = 1 # 等待应答
            else:
                self.pro_ask_flag = 0  # 等待应答

            self.ser_send_data_ascii(at_cmd_e_bin)
            msg = "\r启动无协议升级模式: " + at_cmd_e_bin
            self.signal_write_msg.emit(msg)  # 发送日志消息
            msg = "\r1. 单击 文件-打开升级文件菜单,选择正确的升级文件(*.bin);\r" \
                  "2. 单击 发送固件 按钮,等待数据发送完成，升级较慢，耐心等待;\r" \
                  "3. 数据发送完成后(进度条100%),单击 升级完成,重启设备 按钮,启动新固件;\r" \
                  "4. 如升级后10秒内未进行第3步操作复位设备,设备将不会启用新固件;\r" \
                  "5. 注意!注意!注意! 进入升级模式后、升级完成前不要进行任何串口操作!!!\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息
            self.btn_iap_nopro_start.setEnabled(1)
            self.btn_iap_nopro_reboot.setEnabled(1)
            self.btn_iap_start_US.setEnabled(0)

    # 启动数据发送
    def btn_send_nopro_func(self):

        if not self.is_serial_open():
            return False

        elif not self.is_open_file():
            return False

        else:
            self.cbx_set_hex_mode.setChecked(0)
            self.ser_send_cnt = 0

            msg = "\r=== 正在发送数据，升级完成前不要进行任何操作, 请耐心等待... ===\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息

            if self.cbx_iap_type.currentIndex() == 0: # Hex
                self.th_send_hex = threading.Thread(target=self.th_send_ascii_fun)
                self.th_send_hex.start()
            else: # bin, fst
                self.th_send_bin = threading.Thread(target=self.th_send_bin_fun)
                self.th_send_bin.start()

    # 升级完成, 发送重启命令
    def btn_end_nopro_func(self):

        if not self.is_serial_open():
            return False

        else:
            self.ser_send_cnt = 0

            if self.pro_ask_en:
                self.pro_ask_flag = 1  # 等待应答
            else:
                self.pro_ask_flag = 0  # 等待应答

            at_cmd_rst = "at+rst\r\n"
            print(at_cmd_rst)
            self.ser_send_data_ascii(at_cmd_rst)
            msg = "\r升级完成,正在重启设备,请稍后...: " + at_cmd_rst
            self.signal_write_msg.emit(msg)  # 发送日志消息
            self.btn_iap_nopro_start.setEnabled(0)
            self.btn_iap_nopro_reboot.setEnabled(0)
            self.btn_iap_start_US.setEnabled(1)
            # self.cbx_set_hex_mode.setChecked(1)
            # self.is_use_pro = True  # 使用协议
    # 无协议升级模式 end===========================================================================


    # 打开端口按键函数
    def btn_open_serial_func(self):
        if self.cbx_comm_ck.currentIndex() == 0 or self.cbx_comm_ck.currentIndex() == 1:
            if not self.ser.is_open:
                try:
                    self.ser = self.port_open(self.cbx_com.currentText(), self.cbx_band.currentText())
                    if self.ser.is_open:

                        self.ini_com = self.cbx_com.currentText()
                        self.ini_band = self.cbx_band.currentText()
                        self.Write_to_config() # 保存串口配置

                        self.th_ser_recv.set_app_init(self.ser)
                        if self.cbx_set_hex_mode.isChecked():
                            self.th_ser_recv.set_hex_show(1)
                        else:
                            self.th_ser_recv.set_hex_show(0)
                        self.th_ser_recv.start()

                        self.btn_open_serial.setText("关闭端口")
                        msg = "\r\n串口已打开...\r"
                        self.signal_write_msg.emit(msg)  # 发送日志消息
                        msg = "串口号: " + str(self.ser.port) + "\r波特率: " + str(self.ser.baudrate)  + "bps\r\n"
                        self.signal_write_msg.emit(msg)  # 发送日志消息
                    else:
                        msg = "串口打开失败...\r\n"
                        self.signal_write_msg.emit(msg)  # 发送日志消息

                except Exception as e:
                    print(e)

            else:
                self.ser.close()
                if not self.ser.is_open:
                    msg = "\r\n串口已关闭...\r\n"
                    self.signal_write_msg.emit(msg)  # 发送日志消息
                    self.btn_open_serial.setText("打开端口")
                else:
                    msg = "串口关闭失败...\r\n"
                    self.signal_write_msg.emit(msg)  # 发送日志消息

    def is_serial_open(self):
        if not self.ser.is_open:
            msg = "\r === 端口未打开,请打开端口... ===\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息
            return False
        else:
            return True
    def is_open_file(self):
        if self.file_path_name == "":
            msg = "\r=== 请先打开升级文件, 操作: 文件->打开升级文件... ===\r"
            self.signal_write_msg.emit(msg)  # 发送日志消息
            return False
        else:
            return True

    # 清空接收窗口
    def btn_clean_recv_win_fucn(self):
        self.textEdit_recv.clear()

    # 修改com口
    def cbx_com_func(self):
        pass
    # 修改波特率
    def cbx_band_func(self):
        pass
    # 选择芯片类型
    def cbx_chip_func(self):
        if self.cbx_chip.currentIndex() == 1:
            self.lineEdit_6.setText("0x08000000")
            self.lineEdit_7.setText("0x8000, 32K")
            self.lineEdit_8.setText("0x08008000")
            self.lineEdit_9.setText("0x19000, 100K")
            self.lineEdit_10.setText("0x08021000")
            self.lineEdit_11.setText("0x19000, 100K")
            self.lineEdit_19.setText("0x0803A000")
            self.lineEdit_20.setText("0x6000, 24K")
        if self.cbx_chip.currentIndex() == 2:
            self.lineEdit_6.setText("0x08000000")
            self.lineEdit_7.setText("0x8000, 32K")
            self.lineEdit_8.setText("0x08008000")
            self.lineEdit_9.setText("0x29000, 164K")
            self.lineEdit_10.setText("0x08031000")
            self.lineEdit_11.setText("0x29000, 164K")
            self.lineEdit_19.setText("0x0805A000")
            self.lineEdit_20.setText("0x6000, 24K")
        if self.cbx_chip.currentIndex() == 3:
            self.lineEdit_6.setText("0x08000000")
            self.lineEdit_7.setText("0x10000, 64K")
            self.lineEdit_8.setText("0x08010000")
            self.lineEdit_9.setText("0x32000, 200K")
            self.lineEdit_10.setText("0x08042000")
            self.lineEdit_11.setText("0x32000, 200K")
            self.lineEdit_19.setText("0x08074000")
            self.lineEdit_20.setText("0xC000, 48K")

    def cbx_fst_pro_str_func(self):
        # 协议版本 暂时只支持V2.0
        temp = 0
        if "2." in self.cbx_pro_ver.currentText():
            temp = 0x20
        elif "3." in self.cbx_pro_ver.currentText():
            temp = 0x30
        self.iap_pro_str["pro_ver"][0] = temp
        # 设备类型
        if "通用" in self.cbx_pro_dev_type.currentText():
            temp = 0xFF
        elif "驾考" in self.cbx_pro_dev_type.currentText():
            temp = 0x51
        elif "检测" in self.cbx_pro_dev_type.currentText():
            temp = 0xF1
        self.iap_pro_str["dev_type"][0]= temp
        # 设备地址
        if "通用" in self.cbx_pro_dev_type.currentText():
            temp = 0xFF
        elif "01" in self.cbx_pro_dev_type.currentText():
            temp = 0x51
        self.iap_pro_str["dev_addr"][0] = temp
        # 命令标识
        self.iap_pro_str["cmd_tag"][0] = self.cbx_pro_cmd_ask.currentIndex()
        # 应答标识
        # self.iap_pro_str["ask_tag"][0] = 0


    def cbx_US_func(self):
        # 应答
        self.iap_cmd_US["ask_type"][0] = self.cbx_pack_gap.currentIndex()
        # 包间隔
        self.iap_cmd_US["pack_gap"][0]= int(self.cbx_pack_gap.currentText())
        self.delay_ms = int(self.cbx_pack_gap.currentText())
        # 包大小
        self.iap_cmd_US["pack_size"][0] = int(self.cbx_pack_size.currentText())
        self.pack_size = int(self.cbx_pack_size.currentText())
        # 设备类型 TODO 待完善
        self.iap_cmd_US["dev_type"][0] = self.cbx_pack_size.currentIndex()
        # 芯片类型 4字节 TODO 待完善
        # self.iap_cmd_US["chip_type"][0] = int(self.cbx_pack_size.currentText())
        # 升级类型
        self.iap_cmd_US["up_type"][0] = self.cbx_up_type.currentIndex()
        # 软件版本
        temp = int(self.cbx_soft_ver.currentText(), 16)
        self.iap_cmd_US["soft_ver"][0] = int(temp & 0xff)
        self.iap_cmd_US["soft_ver"][1] = int(temp >> 8)
        # 硬件版本
        temp = int(self.cbx_hard_ver.currentText(), 16)
        self.iap_cmd_US["hard_ver"][0] = int(temp & 0xff)
        self.iap_cmd_US["hard_ver"][1] = int(temp >> 8)
        # 固件版本
        temp = int(self.cbx_iap_ver.currentText(), 16)
        self.iap_cmd_US["iap_ver"][0] = int(temp & 0xff)
        self.iap_cmd_US["iap_ver"][1] = int(temp >> 8)
        # 固件大小 4字节
        temp = int(self.lineEdit_iap_size.text(), 10)
        self.iap_cmd_US["iap_size"][0] = int(temp & 0xff)
        self.iap_cmd_US["iap_size"][1] = int((temp >> 8) & 0xff)
        self.iap_cmd_US["iap_size"][2] = int((temp >> 16) & 0xff)
        self.iap_cmd_US["iap_size"][3] = int((temp >> 24) & 0xff)
        # 加密方式
        self.iap_cmd_US["iap_encode"][0] = self.cbx_iap_encode.currentIndex()
        # 保留 8字节
        # self.iap_cmd_US["iap_size"][0] = int(self.cbx_pack_size.currentText())

# 以下为菜单栏操作函数 ===================================================================================================
    # 打开文件
    def menu_open_file_func(self):
        self.tab_status.setWindowIconText("状态信息")
        self.file_path_name, self.file_type = QFileDialog.getOpenFileName(self,
                              caption = "选取升级固件(*.hex, *.bin, *.fst)",
                              directory = "./user_app/",                # 设置文件扩展名过滤,注意用双分号间隔
                              filter = "All Files (*);;Bin Files (*.bin);;Hex Files (*.hex);;Fst Files (*.fst)",
                              options = QFileDialog.ShowDirsOnly)
        print(self.file_path_name, self.file_type)
        msg = ("\r文件路径: " + self.file_path_name + "\r")
        self.signal_write_msg.emit(msg)  # 发送日志消息

        filetype = os.path.splitext(self.file_path_name)[1]
        msg = "文件类型: " + filetype + "\r"
        self.signal_write_msg.emit(msg)  # 发送日志消息

        if filetype == ".bin":
            self.cbx_iap_type.setCurrentText("bin")
            if self.file_path_name is not None:
                with open(file=self.file_path_name, mode='rb') as file:
                    data = file.read()
                    msg = str(binascii.b2a_hex(data))[2:-1]
                    self.textEdit_data.setText(str(msg))
                    self.file_size = len(data)
                    self.lineEdit_iap_size.setText(str(self.file_size))
                    msg = "文件大小: " + str(self.file_size) + " 字节\r"
                    self.signal_write_msg.emit(msg)  # 发送日志消息
                    msg = "===选择的固件为 *.bin 文件, 请注意编译地址 !===\r\r"
                    self.signal_write_msg.emit(msg)  # 发送日志消息

        elif filetype == ".hex":
            self.cbx_iap_type.setCurrentText("hex")
            if self.file_path_name is not None:
                with open(file=self.file_path_name, mode='r+') as file:
                    data = file.read()
                    self.ser_send_buf = data
                    self.textEdit_data.setText(data)
                    self.file_size = len(data)
                    self.lineEdit_iap_size.setText(str(self.file_size))
                    msg = "文件大小: " + str(self.file_size) + " 字节\r"
                    self.signal_write_msg.emit(msg)  # 发送日志消息
                    msg = "===选择的固件为 *.hex 文件, 请核对编译地址 !===\r"
                    self.signal_write_msg.emit(msg)  # 发送日志消息

        elif filetype == ".fst":
            self.cbx_iap_type.setCurrentText("fst")
            if self.file_path_name is not None:
                with open(file=self.file_path_name, mode='r+') as file:
                    data = file.read()
                    self.textEdit_data.setText(data)
                    self.file_size = len(data)
                    self.lineEdit_iap_size.setText(str(self.file_size))
                    msg = "文件大小: " + str(self.file_size) + " 字节\r"
                    self.signal_write_msg.emit(msg)  # 发送日志消息

        else:
            msg = "\r\n=== 不支持的文件类型, 请选择正确的升级文件(*.bin,*.hex,*.fst) ===\r\n"
            self.signal_write_msg.emit(msg)  # 发送日志消息

    # 菜单-关于
    def menu_about_my_func(self):
        msg = "\r欢迎使用弗斯特MCU在线升级工具...\r"
        self.signal_write_msg.emit(msg)  # 发送日志消息
    # 菜单-帮助
    def menu_help_doc_func(self):
        msg = '''
        \r1. 文件-打开升级文件, 选择要升级的固件
        \r2. 选择通讯方式, 打开端口, 其余可保持默认
        \r3. 检查无误后, 单击启动升级按键, 等待升级完成\r
        '''
        self.signal_write_msg.emit(msg)  # 发送日志消息
    # 菜单-小工具
    def menu_tools_make_func(self):
        pass

    # 保存配置文件
    def Write_to_config(self):
        confing = configparser.ConfigParser()
        # 写入配置文件
        confing.add_section('serial') #添加section
        # 添加值
        confing.set('serial', 'com', self.ini_com)
        confing.set('serial', 'band', self.ini_band)
        # 写入文件
        with open('config.ini', 'w') as fw:
            confing.write(fw)

    # 加载现有配置文件
    def read_from_config(self):
        try:
            confing = configparser.ConfigParser()
            # 读取配置文件
            confing.read("config.ini")
            self.ini_com = confing.get('serial', 'com')
            self.ini_band = confing.get('serial', 'band')
        except Exception as e:
            self.ini_com = "COM1"
            self.ini_band = "115200"
            print(e)

    # 设置是否为十六进制通信模式
    def set_comm_hex_fun(self):
        if self.cbx_set_hex_mode.isChecked():
            print('十六进制通信模式')
            self.th_ser_recv.set_hex_show(1) # Hex模式
        else:
            print('ascii 通信模式')
            self.th_ser_recv.set_hex_show(0)  # ascii模式

    # 一问一答模式, 默认
    def cbx_is_ask_and_answer_func(self):
        if self.cbx_is_ask_and_answer.isChecked():
            self.pro_ask_en = True
            self.cbx_ask_type.setCurrentIndex(0)
        else:
            self.cbx_ask_type.setCurrentIndex(1)
            self.pro_ask_en = False

    # 写消息到接收框
    def signal_write_msg_func(self, msg):
        self.textEdit_recv.insertPlainText(msg)
        self.textEdit_recv.moveCursor(QtGui.QTextCursor.End)# 滚动条移动到结尾
        pass

    # 通信方式选择
    def cbx_comm_ck_func(self):
        if self.cbx_comm_ck.currentIndex() == 0: # rs232
            self.cbx_band.setEnabled(True)
            self.cbx_com.setEnabled(True)
            self.cbx_ip.setEnabled(False)
            self.lineEdit_port.setEnabled(False)
            msg = '选择通讯方式: 串口RS232\n'
            self.signal_write_msg.emit(msg)

        elif self.cbx_comm_ck.currentIndex() == 1: # rs485
            self.cbx_band.setEnabled(True)
            self.cbx_com.setEnabled(True)
            self.cbx_ip.setEnabled(False)
            self.lineEdit_port.setEnabled(False)
            msg = '选择通讯方式: 串口RS485\n'
            self.signal_write_msg.emit(msg)

        elif self.cbx_comm_ck.currentIndex() == 2: # tcp 服务器
            self.get_local_ip()
            self.label_26.setText("本机IP地址")
            self.label_27.setText("监听端口号")
            self.cbx_band.setEnabled(False)
            self.cbx_com.setEnabled(False)
            self.cbx_ip.setEnabled(True)
            self.lineEdit_port.setEnabled(True)
            msg = '选择通讯方式: TCP服务器模式\n'
            self.signal_write_msg.emit(msg)

        elif self.cbx_comm_ck.currentIndex() == 3: # tcp 客户端

            L_List = ["192.168.1.234", "10.0.0.234", ]
            self.cbx_ip.clear()
            self.cbx_ip.addItems(L_List)

            self.label_26.setText("目标IP地址")
            self.label_27.setText("目标端口号")
            self.cbx_band.setEnabled(False)
            self.cbx_com.setEnabled(False)
            self.cbx_ip.setEnabled(True)
            self.lineEdit_port.setEnabled(True)
            msg = '选择通讯方式: TCP客户端模式\n'
            self.signal_write_msg.emit(msg)

    # 获取本机ip
    def get_local_ip(self):
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # noinspection PyBroadException
        try:
            s.connect(('114.114.114.114', 80))
            my_addr = s.getsockname()[0]
            self.cbx_ip.removeItem(0)
            self.cbx_ip.addItem(str(my_addr))

        except Exception:

            # noinspection PyBroadException
            try:
                my_addr = socket.gethostbyname(socket.gethostname())
                self.cbx_ip.addItem(str(my_addr))
            except Exception:
                self.status.showMessage("无法获取ip，请连接网络！\n")
        finally:
            s.close()

    # 串口发送数据(ascii)
    def ser_send_data_ascii(self, s_data):

        if self.ser.is_open:
            self.ser_send_buf = s_data.encode("gb2312")
            result: Optional[int] = self.ser.write(self.ser_send_buf)
            self.ser_send_cnt += result
            print("# Send count: %d"% result)
            print("# Total count: %d"%self.ser_send_cnt)

        else:
            self.btn_open_serial_func()

    # 串口发送数据(bin)
    def ser_send_data_bin(self, s_data):
        self.ser_send_buf = s_data
        result = self.ser.write(self.ser_send_buf)
        self.ser_send_cnt += result
        print("# Send count: %d" % result)
        print("# Total count: %d" % self.ser_send_cnt)

    # 发送hex文件线程
    def th_send_ascii_fun(self):

        if self.file_path_name != "":
            self.ser_send_cnt = 0

            with open(file=self.file_path_name, mode='r') as file:
                # size = os.path.getsize(self.file_path_name)
                start = time.perf_counter()
                for line in file:
                    self.ser_send_data_ascii(line)
                    time.sleep(self.delay_ms/1000)  # 延时 s
                    self.progressBar.setValue((self.ser_send_cnt/self.file_size)*100)

                if self.pro_ask_en:
                    self.pro_ask_flag = 1  # 等待应答

                    while self.pro_ask_flag != 0 and self.pro_ask_flag != 2:
                        time.sleep(0.01)
                        timeout = time.perf_counter() - start
                        if timeout >= 20:
                            print("应答超时...")
                            return
                else:
                    self.pro_ask_flag = 0  # 等待应答

                end = time.perf_counter() - start
                msg = "\r=== 发送完成, 共%d字节, 用时%d秒 ===\r" % (self.file_size, end)
                self.signal_write_msg.emit(msg)  # 发送日志消息

    # 无协议发送bin文件线程
    def th_send_bin_fun(self):

        if self.file_path_name != "":
            send_num = 0

            with open(file=self.file_path_name, mode='rb') as file:
                # size = os.path.getsize(self.file_path_name)
                for i in range(0, self.file_size, self.pack_size):

                    line = file.read(self.pack_size)
                    self.ser_send_data_bin(line)
                    send_num += len(line)
                    send_time = time.perf_counter()
                    time.sleep(self.delay_ms/1000)# 延时 s
                    self.progressBar.setValue((send_num/self.file_size)*100)

                    start = time.perf_counter()

                    if self.pro_ask_en:
                        self.pro_ask_flag = 1  # 等待应答

                        while self.pro_ask_flag != 0 and self.pro_ask_flag != 2:
                            time.sleep(0.01)
                            timeout = time.perf_counter() - send_time
                            if timeout >= 10:
                                self.ser_rx_timeout = True
                                print("应答超时...")
                                return
                    else:
                        self.pro_ask_flag = 0  # 等待应答

                end = time.perf_counter()-start
            msg = "\r\r=== 发送完成, 共%d字节, 用时%d秒 ===\r"%(self.file_size, end)
            self.signal_write_msg.emit(msg)  # 发送日志消息

   # 无协议发送bin文件线程
    def th_send_bin_fst_fun(self):

        if self.file_path_name != "":


            with open(file=self.file_path_name, mode='rb') as file:

                pack_total = int(self.file_size / self.pack_size) # 总包数
                if (self.file_size % self.pack_size) > 0:
                    pack_total += 1
                self.iap_cmd_UD["pack_total"][0] = (int(pack_total & 0xFF))
                self.iap_cmd_UD["pack_total"][1] = (int(pack_total >> 8))

                pack_index = 0 # 包序号
                send_num = 0 # 发送字节数

                for i in range(0, self.file_size, self.pack_size):

                    self.ser_cmd_buf.clear()
                    # 包序号
                    pack_index += 1
                    self.iap_cmd_UD["pack_index"][0] = (int(pack_index & 0xFF))
                    self.iap_cmd_UD["pack_index"][1] = (int(pack_index >> 8))

                    # 包头 ============================================================
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_head"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_ver"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_type"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("dev_addr"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("cmd_tag"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("ask_tag"))
                    self.ser_cmd_buf.append(0)
                    self.ser_cmd_buf.append(0)
                    # 数据体 ===========================================================
                    self.ser_cmd_buf.extend(self.iap_cmd_UD.get("cmd_main"))
                    self.ser_cmd_buf.extend(self.iap_cmd_UD.get("cmd_sub"))
                    self.ser_cmd_buf.extend(self.iap_cmd_UD.get("pack_total"))
                    self.ser_cmd_buf.extend(self.iap_cmd_UD.get("pack_index"))

                    # 升级文件内容 ====================================================
                    line = file.read(self.pack_size)  # 读取数据内容
                    t_len = len(line)
                    # 升级数据长度
                    self.iap_cmd_UD["data_num"][0] = (int(t_len & 0xFF))
                    self.iap_cmd_UD["data_num"][1] = (int(t_len >> 8))
                    self.ser_cmd_buf.extend(self.iap_cmd_UD.get("data_num"))
                    self.ser_cmd_buf.extend(line)

                    t_data = len(self.ser_cmd_buf[10:])
                    self.ser_cmd_buf[8] = (int(t_data & 0xFF))
                    self.ser_cmd_buf[9] = (int(t_data >> 8))

                    send_num += t_data
                    # 包尾 ============================================================
                    crc_buf = self.ser_cmd_buf[3:]
                    self.iap_pro_str["crc"][0], self.iap_pro_str["crc"][1] = crc.calc_crc_hex(crc_buf)
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("crc"))
                    self.ser_cmd_buf.extend(self.iap_pro_str.get("pro_tail"))
                    self.ser_send_data_bin(self.ser_cmd_buf)
                    self.pro_ask_flag = 1
                    # print(self.ser_cmd_buf)

                    msg = "\r\n发送升级数据包: cmd = UD\r\n"
                    self.signal_write_msg.emit(msg)  # 发送日志消息

                    send_time = time.perf_counter()

                    time.sleep(self.delay_ms/1000)# 延时 s
                    self.progressBar.setValue((send_num/self.file_size)*100)

                    start = time.perf_counter()

                    if self.pro_ask_en:
                        self.pro_ask_flag = 1  # 等待应答

                        while self.pro_ask_flag != 0 and self.pro_ask_flag != 2:
                            time.sleep(0.1)
                            timeout = time.perf_counter() - send_time
                            if timeout >= 10:
                                self.ser_rx_timeout = True
                                print("应答超时...")
                                return
                    else:
                        self.pro_ask_flag = 0  # 等待应答

                end = time.perf_counter()-start
            msg = "\r\r=== 发送完成, 共%d字节, 用时%d秒 ===\r"%(self.file_size, end)
            self.signal_write_msg.emit(msg)  # 发送日志消息




if __name__ == '__main__':
    app = QApplication(sys.argv)
    ser_recv = SerialRecvThread() # 串口接收线程
    MainWin = MainLogic(ser_recv)
    MainWin.show()
    sys.exit(app.exec_())

# END ========================================================================

